load("../src/sim-0.26-debug.js");
load("../src/switch.js");
load("unittest.js");

if (!console) {
	var console = {};
	if (print) console.log = print;
	else console.log = function () {};
}

var Host = {
	start: function (network, result) {
		this.network = network;
		this.result = result;
	},
	recv: function (src, sport, dst, dport, data, size) {
		this.result.push([src, dst, dport, this.time()].join("/"));
	},
	transmit: function (dst, size, time, dport) {
		if (!dport) dport = 0;
		this.setTimer(time).done(function (dest, port) {
			this.network.transmit(this.ip, 0, dest, port, size);
		}, this, [dst, dport]);
	}
};

var TestTxSwitch = UnitTest.extend({
	setup: function () {
		this.sim = new Sim();
		this.sw = [];
		this.hosts = [];
		this.results = [];
		
		for (var i = 0; i < 3; i ++) {
			this.sw.push(this.sim.addEntity(Switch));
			this.sw[i].init(6, 100);
		}
		
		for (var i = 0; i < 12; i++) {
			var sw, port, ip, host;
			ip  = 101 + i;
			port = i % 4;
			s = this.sw[Math.floor(i / 4)];
			host = this.sim.addEntity(Host, s, this.results);
			host.ip = ip;
			this.hosts.push(host);
			s.connect(host, port);
		}
		
		this.sw[0].connectSwitch(4, this.sw[1], 4);
		this.sw[1].connectSwitch(5, this.sw[2], 4);
		this.sim.setLogger(print);
	},
	
	test12: function () {
		this.hosts[0].transmit(102, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/102/0/11"]);
	},
	
	test12_12: function () {
		this.hosts[0].transmit(102, 1000, 0);
		this.hosts[0].transmit(102, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/102/0/11", "101/102/0/21"]);
	},
	
	test12_13: function () {
		this.hosts[0].transmit(102, 1000, 0);
		this.hosts[0].transmit(103, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/102/0/11", "101/103/0/21"]);
	},
	
	test12_34: function () {
		this.hosts[0].transmit(102, 1000, 0);
		this.hosts[2].transmit(104, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/102/0/11", "103/104/0/11"]);
	},
	
	test12_32: function () {
		this.hosts[0].transmit(102, 1000, 0);
		this.hosts[2].transmit(102, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/102/0/20", "103/102/0/21"]);
	},
	
	/*** Crossing one switch */
	test15: function () {
		this.hosts[0].transmit(105, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/105/0/12"]);
	},
	
	test15_32: function () {
		this.hosts[0].transmit(105, 1000, 0);
		this.hosts[2].transmit(102, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["103/102/0/11", "101/105/0/12"]);
	},
	test15_67: function () {
		this.hosts[0].transmit(105, 1000, 0);
		this.hosts[5].transmit(107, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["106/107/0/11", "101/105/0/12"]);
	},
	
	test15_26: function () {
		this.hosts[0].transmit(105, 1000, 0);
		this.hosts[1].transmit(106, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/105/0/21", "102/106/0/22"]);
	},
	
	test15_25: function () {
		this.hosts[0].transmit(105, 1000, 0);
		this.hosts[1].transmit(105, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["102/105/0/21", "101/105/0/22"]);
	},
	test15_65: function () {
		this.hosts[0].transmit(105, 1000, 0);
		this.hosts[5].transmit(105, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["106/105/0/20", "101/105/0/21"]);
	},
	test15_16: function () {
		this.hosts[0].transmit(105, 1000, 0);
		this.hosts[0].transmit(106, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/105/0/12", "101/106/0/22"]);
	},
	
	/** Crosses two switches */
	test19: function () {
		this.hosts[0].transmit(109, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/109/0/13"]);
	},
	test19_110: function () {
		this.hosts[0].transmit(109, 1000, 0);
		this.hosts[0].transmit(110, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["101/109/0/13", "101/110/0/23"]);
	},
	test19_29: function () {
		this.hosts[0].transmit(109, 1000, 0);
		this.hosts[1].transmit(109, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["102/109/0/22", "101/109/0/23"]);
	},
	test19_49: function () {
		this.hosts[0].transmit(109, 1000, 0);
		this.hosts[3].transmit(109, 1000, 0);
	
		this.sim.simulate(100);
		this.eqList(this.results, ["104/109/0/22", "101/109/0/23"]);
	},
});

var TestSpanningTree = UnitTest.extend({

	testOneSwitch: function () {
		var sim = new Sim();
		var sw = sim.addEntity(Switch);
		sw.init(4, 100);
		
		this.eqList(sw.traceroute(1,2), []);
		
		for (var i = 0; i < 4; i ++) {
			var host = sim.addEntity(Host, sw);
			host.ip = i;
			sw.connect(host, i);
		}
		
		this.eqList(sw.traceroute(1,2), ["1:1", "1:2"]);
		this.eqList(sw.traceroute(1,1), ["1:1", "1:1"]);
		this.eqList(sw.traceroute(2,40), ["1:2"]);
		this.eqList(sw.traceroute(20, 1), []);
	},
	
	testTwoSwitchConnectedAfter: function () {
		var sim = new Sim();
		var sw1 = sim.addEntity(Switch);
		var sw2 = sim.addEntity(Switch);
		sw1.init(5, 100);
		sw2.init(5, 100);
		for (var i = 0; i < 8; i ++) {
			if (i < 4) {
				var host = sim.addEntity(Host, sw1);
				host.ip = i;
				sw1.connect(host, i);
			} else {
				var host = sim.addEntity(Host, sw2);
				host.ip = i;
				sw2.connect(host, i - 4);
			}
		}
		

		this.eqList(sw1.traceroute(1,5), "1:1".split("-"));
		sw1.connectSwitch(4, sw2, 4);
		
		this.eqList(sw1.traceroute(1,5), "1:1-1:4-2:4-2:1".split("-"));
		this.eqList(sw1.traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sw2.traceroute(6,2), "2:2-2:4-1:4-1:2".split("-"));
		this.eqList(sw1.traceroute(6,2), "1:4-1:2".split("-"));
	},
	
	testTwoSwitchConnectedBefore: function () {
		var sim = new Sim();
		var sw1 = sim.addEntity(Switch);
		var sw2 = sim.addEntity(Switch);
		sw1.init(5, 100);
		sw2.init(5, 100);
		
		sw1.connectSwitch(4, sw2, 4);
		
		for (var i = 0; i < 8; i ++) {
			if (i < 4) {
				var host = sim.addEntity(Host, sw1);
				host.ip = i;
				sw1.connect(host, i);
			} else {
				var host = sim.addEntity(Host, sw2);
				host.ip = i;
				sw2.connect(host, i - 4);
			}
		}
		
		this.eqList(sw1.traceroute(1,5), "1:1-1:4-2:4-2:1".split("-"));
		this.eqList(sw1.traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sw2.traceroute(6,2), "2:2-2:4-1:4-1:2".split("-"));
		this.eqList(sw1.traceroute(6,2), "1:4-1:2".split("-"));
	},
	
	testTwoSwitchConnectedDuring: function () {
		var sim = new Sim();
		var sw1 = sim.addEntity(Switch);
		var sw2 = sim.addEntity(Switch);
		sw1.init(5, 100);
		sw2.init(5, 100);
		var host, port, ip;
		
		port = 0; ip = 0;
		host = sim.addEntity(Host, sw1); host.ip = ip; sw1.connect(host, port);
		port = 0; ip = 4;
		host = sim.addEntity(Host, sw2); host.ip = ip; sw2.connect(host, port);
		
		this.eqList(sw1.traceroute(0,2), "1:0".split("-"));
		this.eqList(sw1.traceroute(0,5), "1:0".split("-"));
		this.eqList(sw2.traceroute(4,2), "2:0".split("-"));
		this.eqList(sw1.traceroute(6,2), []);
		
		
		port = 1; ip = 1;
		host = sim.addEntity(Host, sw1); host.ip = ip; sw1.connect(host, port);
		port = 1; ip = 5;
		host = sim.addEntity(Host, sw2); host.ip = ip; sw2.connect(host, port);

		this.eqList(sw1.traceroute(0,1), "1:0-1:1".split("-"));
		this.eqList(sw1.traceroute(1,2), "1:1".split("-"));
		this.eqList(sw1.traceroute(1,5), "1:1".split("-"));
		this.eqList(sw2.traceroute(5,2), "2:1".split("-"));
		this.eqList(sw1.traceroute(5,2), []);
		
		sw1.connectSwitch(4, sw2, 4);
		
		this.eqList(sw1.traceroute(1,2), "1:1".split("-"));
		this.eqList(sw1.traceroute(1,5), "1:1-1:4-2:4-2:1".split("-"));
		this.eqList(sw2.traceroute(5,1), "2:1-2:4-1:4-1:1".split("-"));
		this.eqList(sw1.traceroute(5,1), "1:4-1:1".split("-"));
		
		port = 2; ip = 2;
		host = sim.addEntity(Host, sw1); host.ip = ip; sw1.connect(host, port);
		port = 2; ip = 6;
		host = sim.addEntity(Host, sw2); host.ip = ip; sw2.connect(host, port);
		
		this.eqList(sw1.traceroute(1,5), "1:1-1:4-2:4-2:1".split("-"));
		this.eqList(sw1.traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sw2.traceroute(6,2), "2:2-2:4-1:4-1:2".split("-"));
		this.eqList(sw1.traceroute(6,2), "1:4-1:2".split("-"));
		
		
		port = 3; ip = 3;
		host = sim.addEntity(Host, sw1); host.ip = ip; sw1.connect(host, port);
		port = 3; ip = 7;
		host = sim.addEntity(Host, sw2); host.ip = ip; sw2.connect(host, port);
		
		this.eqList(sw1.traceroute(1,5), "1:1-1:4-2:4-2:1".split("-"));
		this.eqList(sw1.traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sw2.traceroute(6,2), "2:2-2:4-1:4-1:2".split("-"));
		this.eqList(sw1.traceroute(6,2), "1:4-1:2".split("-"));
	},
	
	testThreeSwitchConnectedAfter: function () {
		var sim = new Sim();
		var sws = [];
		for (var i = 0; i < 3; i ++) {
			sws.push(sim.addEntity(Switch));
			sws[i].init(6, 100);
		}
		
		for (var i = 0; i < 12; i ++) {
			var sw, port;
			sw = sws[Math.floor(i / 4)];
			port = i % 4;
			
			var host = sim.addEntity(Host, sw);
			host.ip = i;
			sw.connect(host, port);
		}
		
		sws[0].connectSwitch(4, sws[1], 4);
		sws[1].connectSwitch(5, sws[2], 4);
		
		this.eqList(sws[0].traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sws[0].traceroute(1,6), "1:1-1:4-2:4-2:2".split("-"));
		this.eqList(sws[0].traceroute(1,9), "1:1-1:4-2:4-2:5-3:4-3:1".split("-"));
		this.eqList(sws[1].traceroute(4,8), "2:0-2:5-3:4-3:0".split("-"));
		this.eqList(sws[2].traceroute(8,0), "3:0-3:4-2:5-2:4-1:4-1:0".split("-"));
		this.eqList(sws[2].traceroute(8,4), "3:0-3:4-2:5-2:0".split("-"));
	},
	
	testThreeSwitchConnectedBefore: function () {
		var sim = new Sim();
		var sws = [];
		for (var i = 0; i < 3; i ++) {
			sws.push(sim.addEntity(Switch));
			sws[i].init(6, 100);
		}
		

		sws[0].connectSwitch(4, sws[1], 4);
		sws[1].connectSwitch(5, sws[2], 4);
		
		for (var i = 0; i < 12; i ++) {
			var sw, port;
			sw = sws[Math.floor(i / 4)];
			port = i % 4;
			
			var host = sim.addEntity(Host, sw);
			host.ip = i;
			sw.connect(host, port);
		}
		
		
		this.eqList(sws[0].traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sws[0].traceroute(1,6), "1:1-1:4-2:4-2:2".split("-"));
		this.eqList(sws[0].traceroute(1,9), "1:1-1:4-2:4-2:5-3:4-3:1".split("-"));
		this.eqList(sws[1].traceroute(4,8), "2:0-2:5-3:4-3:0".split("-"));
		this.eqList(sws[2].traceroute(8,0), "3:0-3:4-2:5-2:4-1:4-1:0".split("-"));
		this.eqList(sws[2].traceroute(8,4), "3:0-3:4-2:5-2:0".split("-"));
	},
	
	testThreeSwitchTriangleConnectedAfter: function () {
		var sim = new Sim();
		var sws = [];
		for (var i = 0; i < 3; i ++) {
			sws.push(sim.addEntity(Switch));
			sws[i].init(6, 100);
		}
		
		for (var i = 0; i < 12; i ++) {
			var sw, port;
			sw = sws[Math.floor(i / 4)];
			port = i % 4;
			
			var host = sim.addEntity(Host, sw);
			host.ip = i;
			sw.connect(host, port);
		}
		
		sws[0].connectSwitch(4, sws[1], 4);
		sws[0].connectSwitch(5, sws[2], 4);
		sws[1].connectSwitch(5, sws[2], 5);
		
		
		this.eqList(sws[0].traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sws[0].traceroute(1,6), "1:1-1:4-2:4-2:2".split("-"));
		this.eqList(sws[0].traceroute(1,9), "1:1-1:5-3:4-3:1".split("-"));
		this.eqList(sws[1].traceroute(4,8), "2:0-2:5-3:5-3:0".split("-"));
		this.eqList(sws[2].traceroute(8,0), "3:0-3:4-1:5-1:0".split("-"));
		this.eqList(sws[2].traceroute(8,4), "3:0-3:5-2:5-2:0".split("-"));
	},
	
	testThreeSwitchTriangleConnectedBefore: function () {
		var sim = new Sim();
		var sws = [];
		for (var i = 0; i < 3; i ++) {
			sws.push(sim.addEntity(Switch));
			sws[i].init(6, 100);
		}
		
		sws[0].connectSwitch(4, sws[1], 4);
		sws[0].connectSwitch(5, sws[2], 4);
		sws[1].connectSwitch(5, sws[2], 5);
		
		for (var i = 0; i < 12; i ++) {
			var sw, port;
			sw = sws[Math.floor(i / 4)];
			port = i % 4;
			
			var host = sim.addEntity(Host, sw);
			host.ip = i;
			sw.connect(host, port);
		}

		
		this.eqList(sws[0].traceroute(1,2), "1:1-1:2".split("-"));
		this.eqList(sws[0].traceroute(1,6), "1:1-1:4-2:4-2:2".split("-"));
		this.eqList(sws[0].traceroute(1,9), "1:1-1:5-3:4-3:1".split("-"));
		this.eqList(sws[1].traceroute(4,8), "2:0-2:5-3:5-3:0".split("-"));
		this.eqList(sws[2].traceroute(8,0), "3:0-3:4-1:5-1:0".split("-"));
		this.eqList(sws[2].traceroute(8,4), "3:0-3:5-2:5-2:0".split("-"));
	},
	
	
	testFourSwitch: function () {


		var eqList = this.eqList;	
		var eq = this.eq;
		function createTopology(order, before, testfn) {

			var sim = new Sim();
			var sws = [];
			var host;
			for (var i = 0; i < 4; i ++) {
				sws.push(sim.addEntity(Switch));
				sws[i].init(4, 100);
			}
			
			if (!before) {
				host = sim.addEntity(Host, sws[0]); host.ip = 0; sws[0].connect(host, 0);			
				host = sim.addEntity(Host, sws[1]); host.ip = 1; sws[1].connect(host, 0);
				host = sim.addEntity(Host, sws[2]); host.ip = 2; sws[2].connect(host, 0);
				host = sim.addEntity(Host, sws[3]); host.ip = 3; sws[3].connect(host, 0);
			}
			
			for (var i = 0; i < order.length; i ++) {
				var from = order[i][0] - 1;
				var to = order[i][1] - 1;
				var fromport = order[i][2];
				var toport = order[i][3];
				sws[from].connectSwitch(fromport, sws[to], toport);
			}
			
			if (before) {
				host = sim.addEntity(Host, sws[0]); host.ip = 0; sws[0].connect(host, 0);			
				host = sim.addEntity(Host, sws[1]); host.ip = 1; sws[1].connect(host, 0);
				host = sim.addEntity(Host, sws[2]); host.ip = 2; sws[2].connect(host, 0);
				host = sim.addEntity(Host, sws[3]); host.ip = 3; sws[3].connect(host, 0);
			}
			
			if (testfn) { testfn(sws); }
			
		}
		
		/* Topology is as follows
		 *  
		 *           2
		 *           o
		 *         1   2
		 *        /      \
		 *       1        2         
		 *      o-2------1-o-3-------1o
		 *     [1]        [3]        [4]
		 */
		function testFlag(sws) {
			// all paths from node 1
			eqList(sws[0].traceroute(0,1), "1:0-1:1-2:1-2:0".split("-"));
			eqList(sws[0].traceroute(0,2), "1:0-1:2-3:1-3:0".split("-"));
			eqList(sws[0].traceroute(0,3), "1:0-1:2-3:1-3:3-4:1-4:0".split("-"));
			
			eqList(sws[1].traceroute(1,0), "2:0-2:1-1:1-1:0".split("-"));
			eqList(sws[1].traceroute(1,2), "2:0-2:2-3:2-3:0".split("-"));
			eqList(sws[1].traceroute(1,3), "2:0-2:2-3:2-3:3-4:1-4:0".split("-"));
			
			eqList(sws[2].traceroute(2,0), "3:0-3:1-1:2-1:0".split("-"));
			eqList(sws[2].traceroute(2,1), "3:0-3:2-2:2-2:0".split("-"));
			eqList(sws[2].traceroute(2,3), "3:0-3:3-4:1-4:0".split("-"));
			
			eqList(sws[3].traceroute(3,0), "4:0-4:1-3:3-3:1-1:2-1:0".split("-"));
			eqList(sws[3].traceroute(3,1), "4:0-4:1-3:3-3:2-2:2-2:0".split("-"));
			eqList(sws[3].traceroute(3,2), "4:0-4:1-3:3-3:0".split("-"));
		}
		/// [fromnode, tonode, fromnodeport, tonodeport]
		var connectionorder = 		
			[[1, 2, 1, 1], // 1 and 2
			 [1, 3, 2, 1], // 1 and 3
			 [2, 3, 2, 2], // 2 and 3
			 [3, 4, 3, 1]]; // 3 and 4
		createTopology(connectionorder, true, testFlag);
		createTopology(connectionorder, false, testFlag);
		var connectionorder = 		
			[
			 [1, 2, 1, 1], // 1 and 2
			 [2, 3, 2, 2], // 2 and 3
			 [1, 3, 2, 1], // 1 and 3
			 [3, 4, 3, 1]
			 ]; // 3 and 4
		createTopology(connectionorder, true, testFlag);
		createTopology(connectionorder, false, testFlag);
		
		var connectionorder = 		
			[[2, 3, 2, 2], // 2 and 3
			 [1, 2, 1, 1], // 1 and 2
			 [3, 4, 3, 1], // 3 and 4
			[1, 3, 2, 1], // 1 and 3
			 ]; 
		createTopology(connectionorder, true, testFlag);
		createTopology(connectionorder, false, testFlag);
		
		var connectionorder = 		
			[
			 [2, 3, 2, 2], // 2 and 3
			 [1, 3, 2, 1], // 1 and 3
			 [3, 4, 3, 1], // 3 and 4
			 [1, 2, 1, 1], // 1 and 2
			 ]; 
		createTopology(connectionorder, true, testFlag);
		createTopology(connectionorder, false, testFlag);
		
		/* Square topology
		 *   
		 *   [4]                  [3]
		 *   o2-------------------2o
		 *   1                     1
		 *   |                     |
		 *   |                     |
		 *   |                     |
		 *   |                     |
		 *   2                     2
		 *   o1-------------------1o
		 *   [1]                  [2]
		 */
		function testsquare(sws) {
			eqList(sws[0].traceroute(0,1), "1:0-1:1-2:1-2:0".split("-"));
			eq(sws[0].traceroute(0,2).length, 6);
			eqList(sws[0].traceroute(0,3), "1:0-1:2-4:1-4:0".split("-"));
			
			eqList(sws[1].traceroute(1,0), "2:0-2:1-1:1-1:0".split("-"));
			eqList(sws[1].traceroute(1,2), "2:0-2:2-3:1-3:0".split("-"));
			eq(sws[1].traceroute(1,3).length, 6);
			
			eq(sws[2].traceroute(2,0).length, 6);
			eqList(sws[2].traceroute(2,1), "3:0-3:1-2:2-2:0".split("-"));
			eqList(sws[2].traceroute(2,3), "3:0-3:2-4:2-4:0".split("-"));
			
			eqList(sws[3].traceroute(3,0), "4:0-4:1-1:2-1:0".split("-"));
			eq(sws[3].traceroute(3,1).length, 6);
			eqList(sws[3].traceroute(3,2), "4:0-4:2-3:2-3:0".split("-"));
		}
		
		var connectionorder = 		
			[
			 [1, 2, 1, 1],
			 [2, 3, 2, 1],
			 [3, 4, 2, 2],
			 [1, 4, 2, 1]
			 ];
		createTopology(connectionorder, true, testsquare);
		createTopology(connectionorder, false, testsquare);
		
		var connectionorder = 		
			[
			 [1, 2, 1, 1],
			 [1, 4, 2, 1],
			 [3, 4, 2, 2],
			 [2, 3, 2, 1]
			 ];
		createTopology(connectionorder, true, testsquare);
		createTopology(connectionorder, false, testsquare);
	}
});



new TestSpanningTree().run();
new TestTxSwitch().run();
		